# ---------------------------------------------------------------
# SPDX-License-Identifier: Artistic-2.0
# ---------------------------------------------------------------
# File Name     : Generator.rakumod
# File Authors  : Aoran Zeng <ccmywish@qq.com>
# Contributors  :  Nul None  <nul@none.org>
# Created On    : <2025-07-12>
# Last Modified : <2025-08-09>
#
# Generates C code from rawstr4c configuration
# ---------------------------------------------------------------

unit package Rawstr4c;

use Rawstr4c::Parser;
use Rawstr4c::EffectiveConfig;
use Rawstr4c::Version;

my class CStringConverter {

  method convert-char($char, $mode) {
    given $mode {
      when 'oct' {
        my $bytes = $char.encode('UTF-8');
        return $bytes.map({ "\\" ~ sprintf("%03o", $_) }).join('');
      }
      when 'hex' {
        my $bytes = $char.encode('UTF-8');
        return $bytes.map({ "\\x" ~ sprintf("%02x", $_) }).join('');
      }
      when 'escape' {
        given $char {
          when '"'  { return '\\"'; }
          when '\\' { return '\\\\'; }
          when "\n" { return '\\n'; }
          when "\t" { return '\\t'; }
          when "\r" { return '\\r'; }
          when "\0" { return '\\0'; }
          default   { return $char; }
        }
      }
      default { die "Unknown translate mode: $mode"; }
    }
  }

  method convert-string($content, $mode) {
    my $result = "";
    for $content.comb -> $char {
      $result ~= self.convert-char($char, $mode);
    }
    return $result;
  }
}



my class CVariableNameGenerator {

  method generate($section) {

    my $config = EffectiveSessionConfig.new($section);

    my $prefix = $config.prefix.string-value;
    my $postfix = $config.postfix.string-value;

    my $no-prefix = $config.no-prefix.bool-value;
    my $no-postfix = $config.no-postfix.bool-value;

    my $name = $config.name.string-value;
    my $namespace = $config.namespace.string-value;
    my $name-literally = $config.name-literally.bool-value;

    # 替换非法字符
    $name = $name.subst(/<-[a..z A..Z 0..9 _]>/, '_', :g);
    # 合并连续的下划线
    $name = $name.subst(/_+/, '_', :g);
    # 移除结尾的下划线
    $name = $name.subst(/_+$/, '');


    my $varname = "";
    if $name-literally {
      # 如果是字面量，直接使用原名
      $varname = $name;
    } else {
      # 否则，按照规则组装变量名
      $varname ~= $prefix if (!$no-prefix) && $prefix;

      $varname ~= "_" if $varname && $namespace;
      $varname ~= $namespace if $namespace;

      $varname ~= "_" if $varname && $name;
      $varname ~= $name if $name;

      $varname ~= "_" if $varname && $postfix && (!$no-postfix);
      $varname ~= $postfix if $postfix && (!$no-postfix);
    }
    return $varname || "unnamed_var";
  }
}


#| 生成 .h 文件或/和 .c 文件，或存储到 @variables 中
my class CVariableGenerator {
  has Hash @.variables;
  has Str  $.c-header-filename is rw;
  has Str  $.c-source-filename is rw;

  method new() {
    self.bless(:variables([]));
  }

  #| C变量名，C变量值, 生成类型
  method add-variable($name, $value, $kind) {
    @.variables.push: {
      name  => $name,
      value => $value,
      kind  => $kind
    };
  }

  #| 生成 C 头文件的内容
  method generate-c-header-file() {
    my $header = qq:to/EOF/;
    #pragma once

    /**
     * Generated by rawstr4c v{Rawstr4c::VERSION}-{Rawstr4c::RELEASE_DATE}
     */

    EOF

    for @.variables -> $var {
      given $var<kind> {
        when 'global-variable-only-header' {
          $header ~= "char {$var<name>}[] = \"{$var<value>}\";\n\n";
        }
        when 'global-variable' {
          $header ~= "extern char {$var<name>}[];\n";
        }
        when 'macro' {
          $header ~= "#define {$var<name>.uc} \"{$var<value>}\"\n\n";
        }
        default {
          die "Unknown variable kind: {$var<kind>}";
        }
      }
    }

    return $header;
  }

  #| 生成 C 源文件的内容
  method generate-c-source-file() {
    my $source = qq:to/EOF/;
    /**
     * Generated by rawstr4c v{Rawstr4c::VERSION}-{Rawstr4c::RELEASE_DATE}
     */

    #include "{$.c-header-filename}"

    EOF

    for @.variables -> $var {
      if $var<kind> eq 'global-variable' {
        $source ~= "char {$var<name>}[] = \"{$var<value>}\";\n";
      }
    }
    return $source;
  }


  method save-files($dest-dir) {

    my $c-header-file = $dest-dir.IO.child($.c-header-filename).Str;

    $c-header-file.IO.spurt(self.generate-c-header-file());
    say "Generated C header file: $c-header-file";

    # 检查是否有 "头、源并存的变量"，如果有就使用并存的头文件和源文件模式
    my $need-gen-c-source-file = @.variables.grep({ $_<kind> eq 'global-variable' }).elems > 0;

    if $need-gen-c-source-file {
      my $c-source-file = $dest-dir.IO.child($.c-source-filename).Str;
      $c-source-file.IO.spurt(self.generate-c-source-file());
      say "Generated C source file: $c-source-file";
    }
  }
}


class Generator {

  has Bool                     $!enable-debug = False; # 是否启用调试模式
  has Rawstr4c::Parser         $.parser;
  has CStringConverter         $.string-converter;
  has CVariableNameGenerator   $.varname-generator;
  has CVariableGenerator       $.variable-generator;

  method new($parser) {
    self.bless(
      :$parser,
      :string-converter(CStringConverter.new),
      :varname-generator(CVariableNameGenerator.new),
      :variable-generator(CVariableGenerator.new)
    );
  }

  method debug() {
    $!enable-debug = True;
  }

  method generate-for-section($section) {
    my $configblock = $section.configblock;
    my $title = $section.title;
    my $rawstr = $section.codeblock;

    # 通过 Raku 的方法 .lines 读到的行中，每一行结尾都有换行符
    # 最后一行，也是如此。然而最后一行的换行符是不应该存在的
    $rawstr = $rawstr.chomp;
    # or use this:
    # $rawstr = $rawstr.subst(/\n$/, '');

    my $config = EffectiveSessionConfig.new($section);

    my $debug-in-config = $config.debug.bool-value;

    return unless $rawstr;

    my $translate-mode = $config.translate-mode.mode-value;
    my $output-mode = $config.output-mode.mode-value;
    my $varname = $.varname-generator.generate($section);

    my $output-h-file = $config.output-h-file.string-value;
    my $output-c-file = $config.output-c-file.string-value;

    if $debug-in-config || $!enable-debug {
      my $language = $config.language.string-value;
      my $prefix = $config.prefix.string-value;
      my $postfix = $config.postfix.string-value;

      say "------ Section: $title ------";
      say "Output mode = $output-mode";
      if $output-mode eq 'global-variable-only-header' {
        say "Output header file = $output-h-file";
      }
      if $output-mode eq 'global-variable' {
        say "Output header file = $output-h-file";
        say "Output source file = $output-c-file";
      }
      say "Translate mode = $translate-mode";
      say "Language = $language";
      say "Prefix  = $prefix";
      say "Postfix = $postfix";
      say "Variable name = $varname";
      say '';
    }

    my $c-string = $.string-converter.convert-string($rawstr, $translate-mode);

    $.variable-generator.c-header-filename = $output-h-file;
    $.variable-generator.c-source-filename = $output-c-file;

    given $output-mode {
      when 'terminal' {
        say 'char ' ~ $varname ~ '[] = "' ~ $c-string ~ '";';
        say "";
      }
      when 'global-variable' {
        $.variable-generator.add-variable($varname, $c-string, 'global-variable');
      }
      when 'global-variable-only-header' {
        $.variable-generator.add-variable($varname, $c-string, 'global-variable-only-header');
      }
      when 'macro' {
        $.variable-generator.add-variable($varname, $c-string, 'macro');
      }
      default {
        die "Illegal output mode: $output-mode";
      }
    }
  }


  method generate() {

    my $root-section = $.parser.root-section;

    # 获取所有需要处理的 sections：包括 root section 和所有子 sections
    my @all-sections = ($root-section, |$root-section.get-all-descendants());

    # 这个 generate-for-section() 要么把变量输出到终端，要么累计到 @variables 中
    for @all-sections -> $section {
      self.generate-for-section($section);
    }

    # 如果有任何变量被添加 (没有被输出到终端)，就保存文件
    if $.variable-generator.variables.elems > 0 {
      my $dest-dir = $.parser.input-file.IO.dirname.Str;
      $.variable-generator.save-files($dest-dir);
    }
  }
}
